// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.DataFormatters {

using System;
using System.Collections;
using System.Text;

using NSpring.Logging.Common;

/// <summary>
/// <para>
/// A DataFormatter implementation that formats event data in a tree structure.  List-type structures (such as arrays and lists)
/// are represented with an L heading, and maps (dictionaries, etc.) with an M heading. A sample:
/// </para>
/// <para>
/// String values are surrounded by quotes and quote-escaped, and date values are surrounded
/// by apostrophes and apostrophe-escaped.
/// </para>
/// <para>
/// <code>
///  &lt;L&gt;
///   |-&lt;L&gt;
///   |  | &quot;one&quot;
///   |  + &quot;two&quot;
///   +-&lt;M&gt;
///      | (&quot;key1&quot;,  &quot;value1&quot;)
///      + (&quot;key2&quot;,  &quot;value2&quot;)
/// </code>
/// </para>
/// </summary>
public sealed class TreeDataFormatter : DataFormatter {
    private static string[] yearStrings                 = Constants.YearStrings;
    private static string[] dateStrings                 = Constants.DateStrings;
    private static string[] millisecondStrings          = Constants.MillisecondStrings;

    private const string TIMESTAMP_FORMAT               = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffzzz";

    private const string LIST_MARKER                    = "<L>";
    private const string MAP_MARKER                     = "<M>";
    
    private const char VERTICAL_LINK_CHAR               = '|';
    private const char VERTICAL_LINK_PASSTHROUGH_CHAR   = '|';
    private const char HORIZONTAL_LINK_CHAR             = '-';
    private const char TERMINATOR_CHAR                  = '+';

    private const string QUOTE                          = "\"";
    private const string APOSTROPHE                     = "'";
    private const string BLANK_STRING                   = "";
    private const string DT                             = "DT";
    private const string ELLIPSIS                       = "...";
    private const string ELEMENT_SEPARATOR              = ", ";
    private const string PAD                            = "  ";
    private const string NULL_VALUE                     = "NULL";
    private const string BACKSLASH                      = "\\";
    private const string BACKSLASH_ESCAPE               = "\\\\";
    private const string APOSTROPHE_ESCAPE              = "\\'";
    private const string QUOTE_ESCAPE                   = "\\\"";
    private static string NEW_LINE_VALUE                = '\n'.ToString();
    private const string NEW_LINE_ESCAPE                = "\\n";
    
    private const char SPACE_CHAR                       = ' ';
    private const char QUOTE_CHAR                       = '"';
    private const char APOSTROPHE_CHAR                  = '\'';
    private const char COMMA_CHAR                       = ',';
    private const char NEW_LINE_CHAR                    = '\n';
    private const char COLON_CHAR                       = ':';
    private const char HYPHEN_CHAR                      = '-';
    private const char PERIOD_CHAR                      = '.';
    private const char T_CHAR                           = 'T';
    private const char P_CHAR                           = 'P';
    private const char H_CHAR                           = 'H';
    private const char M_CHAR                           = 'M';
    private const char S_CHAR                           = 'S';
    private const char BACKSLASH_CHAR                   = '\\';
    private const char OPEN_BRACKET_CHAR                = '[';
    private const char CLOSE_BRACKET_CHAR               = ']';
    private const char OPEN_PARENTHESIS_CHAR            = '(';
    private const char CLOSE_PARENTHESIS_CHAR           = ')';
    
    private static string NEW_LINE                      = new string(new char[] {((char)13), ((char)10)});
    
    private static string[] padding;

    private int leftMarginWidth                         = 0;
    private string prefix                               = null;
    private string suffix                               = null;
    
    static TreeDataFormatter() {
        padding = new string[31];
        padding[0] = BLANK_STRING;
        for(int x = 1; x < padding.Length; x++) {
            padding[x] = BLANK_STRING.PadLeft(x, SPACE_CHAR);
        }
    }
    
    /// <summary>
    /// Constructs a new instance
    /// </summary>
    public TreeDataFormatter() {
        dateTimeFormat = TIMESTAMP_FORMAT;
    }
    
    /// <summary>
    /// Gets and sets the string written before the start of formatted output
    /// </summary>
    public string Prefix {
        get {
            return prefix;
        }
        set {
            prefix = value;
        }
    }
    
    /// <summary>
    /// Gets and sets the string written after the end of formatted output
    /// </summary>
    public string Suffix {
        get {
            return suffix;
        }
        set {
            suffix = value;
        }
    }
    
    /// <summary>
    /// The number of space characters that will be prepended to every line. 
    /// The default is 1
    /// </summary>
    public int LeftMarginWidth {
        get {
            return leftMarginWidth;
        }
        set {
            leftMarginWidth = Math.Min(padding.Length, Math.Max(0, value));
        }
    }
    
    /// <summary>
    /// Constructs a deep copy of this instance
    /// </summary>
    /// <returns>A deep copy of this instance</returns>
    public override DataFormatter Copy() {
        TreeDataFormatter copy = new TreeDataFormatter();
        TypeFormatter[] tfa = TypeFormatters;
        for(int x = 0; x < tfa.Length; x++) {
            copy.AddTypeFormatter(tfa[x].Copy());
        }
        copy.dateTimeFormat = this.dateTimeFormat;
        copy.decimalFormat = this.decimalFormat;
        copy.doubleFormat = this.doubleFormat;
        copy.falseValue = this.falseValue;
        copy.floatFormat = this.floatFormat;
        copy.integerFormat = this.integerFormat;
        copy.isKeySortingEnabled = this.isKeySortingEnabled;
        copy.isListSortingEnabled = this.isListSortingEnabled;
        copy.leftMarginWidth = this.leftMarginWidth;
        copy.longFormat = this.longFormat;
        copy.trueValue = this.trueValue;
        copy.prefix = this.prefix;
        copy.suffix = this.suffix;
        return copy;
    }
    
    /// <summary>
    /// Formats the specified data object
    /// </summary>
    /// <param name="o">The data object to format</param>
    /// <returns>The formatted data</returns>
    public override string Format(object o) {
        StringBuilder sb = new StringBuilder(128);
        Format(o, sb);
        return sb.ToString();
    }

    /// <summary>
    /// Formats the specified data object
    /// </summary>
    /// <param name="o">The data object to format</param>
    /// <param name="sb">The string buffer to which to append the formatted data</param>
    public override void Format(object o, StringBuilder sb) {
        if (o == null) {
            return;
        }
        ArrayList stack = new ArrayList(MaximumDepth + 2);        
        Format(o, sb, stack, false, false);
    }
    
    private int FormatPrefix(StringBuilder sb, ArrayList stack, bool isTerminator) {
        int position = 0;
        int newPosition;
        StackNode node;
        int padWidth;
        string pad;
        for(int x = 0; x < stack.Count; x++) {
            node = (StackNode)stack[x];
            newPosition = node.Position;
            padWidth = (newPosition - position);
            if (padWidth > (padding.Length - 1)) {
                pad = BLANK_STRING.PadLeft(padWidth, SPACE_CHAR);
            }
            else {
                pad = padding[padWidth];
            }
            sb.Append(pad);
            if (x < (stack.Count - 1)) {
                if (node.IsAtEnd) {
                    sb.Append(SPACE_CHAR);
                }
                else {
                    sb.Append(VERTICAL_LINK_PASSTHROUGH_CHAR);
                }
            }
            else {
                if (isTerminator) {
                    sb.Append(TERMINATOR_CHAR);
                }
                else {
                    sb.Append(VERTICAL_LINK_CHAR);
                }
            }
            position = newPosition + 1;
        }
        return position;
    }
    
    private void Format(object data, StringBuilder sb, ArrayList stack, bool isTerminator, bool isDictionaryValue) {
        int position = -1;
        if (stack.Count == 0) {
            sb.Append(padding[leftMarginWidth]);
            position = leftMarginWidth;
        }
        else {
            if (!isDictionaryValue) {
                position = FormatPrefix(sb, stack, isTerminator);
            }
        }
        if (stack.Count > MaximumDepth) {
            sb.Append(SPACE_CHAR).Append(ELLIPSIS);
            return;
        }
        else if (data == null) {
            sb.Append(SPACE_CHAR).Append(NULL_VALUE);
            return;
        }
        Type dataType = data.GetType();
        if (dataType.IsPrimitive) {
            if ((stack.Count > 0) && (!isDictionaryValue)) {
                sb.Append(SPACE_CHAR);
            }
            string format;
            if (dataType == BOOLEAN) {
                sb.Append(((bool)data) ? trueValue : falseValue);
            }
            else if (dataType == INT32) {
                format = integerFormat;
                if (format != null) {
                    sb.Append(((int)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == INT64) {
                format = longFormat;
                if (format != null) {
                    sb.Append(((long)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == SINGLE) {
                format = floatFormat;
                if (format != null) {
                    sb.Append(((float)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == DOUBLE) {
                format = doubleFormat;
                if (format != null) {
                    sb.Append(((double)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == INT16) {
                format = integerFormat;
                if (format != null) {
                    sb.Append(((Int16)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == UINT16) {
                format = integerFormat;
                if (format != null) {
                    sb.Append(((UInt16)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == UINT32) {
                format = integerFormat;
                if (format != null) {
                    sb.Append(((uint)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == UINT64) {
                format = longFormat;
                if (format != null) {
                    sb.Append(((ulong)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == BYTE) {
                format = integerFormat;
                if (format != null) {
                    sb.Append(((byte)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == SBYTE) {
                format = integerFormat;
                if (format != null) {
                    sb.Append(((sbyte)data).ToString(format));
                }
                else {
                    sb.Append(data.ToString());
                }
            }
            else if (dataType == CHAR) {
                sb.Append(QUOTE_CHAR).Append(EscapeString(data.ToString())).Append(QUOTE_CHAR);
            }
        }
        else if (dataType == STRING) {
            if ((stack.Count > 0) && (!isDictionaryValue)) {
                sb.Append(SPACE_CHAR);
            }
            sb.Append(QUOTE_CHAR).Append(EscapeString((string)data)).Append(QUOTE_CHAR);
        }
        else if (dataType == DATE_TIME) {
            if ((stack.Count > 0) && (!isDictionaryValue)) {
                sb.Append(SPACE_CHAR);
            }
            sb.Append(APOSTROPHE_CHAR);
            string format = dateTimeFormat;
            if (format != null) {
                sb.Append(EscapeDateTime(((DateTime)data).ToString(format)));
            }
            else {
                DateTime dateTime = (DateTime)data;
                int year = dateTime.Year;
                if (year < yearStrings.Length) {
                    sb.Append(yearStrings[year]).Append(HYPHEN_CHAR);
                }
                else {
                    sb.Append(year).Append(HYPHEN_CHAR);
                }
                sb.Append(yearStrings[dateTime.Year]).Append(HYPHEN_CHAR);
                sb.Append(dateStrings[dateTime.Month]).Append(HYPHEN_CHAR);
                sb.Append(dateStrings[dateTime.Day]).Append(SPACE_CHAR);
                sb.Append(dateStrings[dateTime.Hour]).Append(COLON_CHAR);
                sb.Append(dateStrings[dateTime.Minute]).Append(COLON_CHAR);
                sb.Append(dateStrings[dateTime.Second]).Append(PERIOD_CHAR);
                sb.Append(millisecondStrings[dateTime.Millisecond]);
                sb.Append(GetLocalTimeZone(dateTime.Month, dateTime.Day));
            }
            sb.Append(APOSTROPHE_CHAR);
        }
        else if (dataType == DECIMAL) {
            if ((stack.Count > 0) && (!isDictionaryValue)) {
                sb.Append(SPACE_CHAR);
            }
            string format = decimalFormat;
            if (format != null) {
                sb.Append(((decimal)data).ToString(format));
            }
            else {
                sb.Append(data.ToString());
            }
        }
        else if (dataType == TIMESPAN) {
            if ((stack.Count > 0) && (!isDictionaryValue)) {
                sb.Append(SPACE_CHAR);
            }
            sb.Append(APOSTROPHE_CHAR);
            TimeSpan ts = (TimeSpan)data;
            sb.Append(P_CHAR);
            sb.Append(ts.Days).Append(DT);
            sb.Append(ts.Hours).Append(H_CHAR);
            sb.Append(ts.Minutes).Append(M_CHAR);
            sb.Append(ts.Seconds);
            if (ts.Milliseconds > 0) {
                sb.Append(PERIOD_CHAR).Append(millisecondStrings[ts.Milliseconds]);
            }
            sb.Append(S_CHAR);
            sb.Append(APOSTROPHE_CHAR);
        }
        else if (dataType.IsArray) {
            if (!isDictionaryValue) {
                if (stack.Count > 0) {
                    sb.Append(HORIZONTAL_LINK_CHAR).Append(LIST_MARKER).Append(NEW_LINE);
                    stack.Add(new StackNode((position + 2), false));
                }
                else {
                    sb.Append(LIST_MARKER).Append(NEW_LINE);
                    stack.Add(new StackNode((position + 1), false));
                }
            }
            StackNode node = (StackNode)stack[stack.Count - 1];
            Array array = (Array)data;
            if (isListSortingEnabled) {
                object[] elements = Sort(array);
                if (elements.Length > 0) {
                    if (elements.Length == 1) {
                        node.IsAtEnd = true;
                        Format(elements[0], sb, stack, true, false);
                    }
                    else {
                        Format(elements[0], sb, stack, false, false);
                        for(int x = 1; x < (elements.Length - 1); x++) {
                            sb.Append(NEW_LINE);
                            Format(elements[x], sb, stack, false, false);
                        }
                        node.IsAtEnd = true;
                        sb.Append(NEW_LINE);
                        Format(elements[elements.Length - 1], sb, stack, true, false);
                    }
                }
            }
            else {
                if (array.Length > 0) {
                    if (array.Length == 1) {
                        Format(array.GetValue(0), sb, stack, true, false);
                    }
                    else {
                        Format(array.GetValue(0), sb, stack, false, false);
                        for(int x = 1; x < (array.Length - 1); x++) {
                            sb.Append(NEW_LINE);
                            Format(array.GetValue(x), sb, stack, false, false);
                        }
                        node.IsAtEnd = true;
                        sb.Append(NEW_LINE);
                        Format(array.GetValue(array.Length - 1), sb, stack, true, false);
                    }
                }
            }
            if (!isDictionaryValue) {
                stack.RemoveAt(stack.Count - 1);
            }
        }
        else if (data is IDictionary) {
            if (!isDictionaryValue) {
                if (stack.Count > 0) {
                    sb.Append(HORIZONTAL_LINK_CHAR).Append(MAP_MARKER).Append(NEW_LINE);
                    stack.Add(new StackNode((position + 2), false));
                }
                else {
                    sb.Append(MAP_MARKER).Append(NEW_LINE);
                    stack.Add(new StackNode((position + 1), false));
                }
            }
            object[] keys;
            IDictionary dictionary = (IDictionary)data;
            if (isKeySortingEnabled) {
                keys = SortKeys(dictionary);
            }
            else {
                keys = GetKeys(dictionary);
            }
            object key, value;
            StackNode node = (StackNode)stack[stack.Count - 1];
            bool terminator;
            for(int x = 0; x < keys.Length; x++) {
                key = keys[x];
                value = dictionary[key];
                if (x > 0) {    sb.Append(NEW_LINE);    }
                terminator = (x == (keys.Length - 1));
                node.IsAtEnd = terminator;
                position = FormatPrefix(sb, stack, terminator);
                sb.Append(SPACE_CHAR).Append(OPEN_PARENTHESIS_CHAR);
                int keyLength = FormatDictionaryKey(key, sb);
                sb.Append(COMMA_CHAR).Append(SPACE_CHAR);
                position += (keyLength + 5);

                if (value is string) {
                    Format(value, sb, stack, terminator, true);
                    sb.Append(CLOSE_PARENTHESIS_CHAR);
                }
                else if (value is Array) {
                    sb.Append(LIST_MARKER).Append(CLOSE_PARENTHESIS_CHAR).Append(NEW_LINE);
                    stack.Add(new StackNode(position, false));
                    Format(value, sb, stack, terminator, true);
                    stack.RemoveAt(stack.Count - 1);
                }
                else if (value is IDictionary) {
                    sb.Append(MAP_MARKER).Append(CLOSE_PARENTHESIS_CHAR).Append(NEW_LINE);
                    stack.Add(new StackNode(position, false));
                    Format(value, sb, stack, terminator, true);
                    stack.RemoveAt(stack.Count - 1);
                }
                else if (value is IEnumerable) {
                    sb.Append(LIST_MARKER).Append(CLOSE_PARENTHESIS_CHAR).Append(NEW_LINE);
                    stack.Add(new StackNode(position, false));
                    Format(value, sb, stack, terminator, true);
                    stack.RemoveAt(stack.Count - 1);
                }
                else {
                    Format(value, sb, stack, terminator, true);
                    sb.Append(CLOSE_PARENTHESIS_CHAR);
                }
            }
            if (!isDictionaryValue) {
                stack.RemoveAt(stack.Count - 1);
            }
        }
        else if (data is IEnumerable) {
            if (!isDictionaryValue) {
                if (stack.Count > 0) {
                    sb.Append(HORIZONTAL_LINK_CHAR).Append(LIST_MARKER).Append(NEW_LINE);
                    stack.Add(new StackNode((position + 2), false));
                }
                else {
                    sb.Append(LIST_MARKER).Append(NEW_LINE);
                    stack.Add(new StackNode((position + 1), false));
                }
            }
            object[] elements;
            StackNode node = (StackNode)stack[stack.Count - 1];
            if (isListSortingEnabled) {
                elements = Sort((IEnumerable)data);
            }
            else {
                elements = GetElements((IEnumerable)data);
            }
            if (elements.Length > 0) {
                if (elements.Length == 1) {
                    node.IsAtEnd = true;
                    Format(elements[0], sb, stack, true, false);
                }
                else {
                    Format(elements[0], sb, stack, false, false);
                    for(int x = 1; x < (elements.Length - 1); x++) {
                        sb.Append(NEW_LINE);
                        Format(elements[x], sb, stack, false, false);
                    }
                    sb.Append(NEW_LINE);
                    node.IsAtEnd = true;
                    Format(elements[elements.Length - 1], sb, stack, true, false);
                }
            }
            if (!isDictionaryValue) {
                stack.RemoveAt(stack.Count - 1);
            }
        }
        else {
            if ((stack.Count > 0) && (!isDictionaryValue)) {
                sb.Append(SPACE_CHAR);
            }
            TypeFormatter formatter = this.GetTypeFormatter(dataType);
            if (formatter != null) {
                if (formatter.IsPassThroughRequested) {
                    sb.Append(formatter.Format(data));
                }
                else {
                    sb.Append(QUOTE_CHAR).Append(EscapeString(formatter.Format(data))).Append(QUOTE_CHAR);
                }
            }
            else {
                sb.Append(QUOTE_CHAR).Append(EscapeString(data.ToString())).Append(QUOTE_CHAR);
            }
        }
    }

    private int FormatDictionaryKey(object data, StringBuilder sb) {
        string dataString;
        if (data == null) {
            sb.Append(NULL_VALUE);
            return NULL_VALUE.Length;
        }
        Type dataType = data.GetType();
        if (dataType.IsPrimitive) {
            string format;
            if (dataType == BOOLEAN) {
                dataString = (((bool)data) ? trueValue : falseValue);
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == INT32) {
                format = integerFormat;
                if (format != null) {
                    dataString = (((int)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == INT64) {
                format = longFormat;
                if (format != null) {
                    dataString = (((long)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == SINGLE) {
                format = floatFormat;
                if (format != null) {
                    dataString = (((float)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == DOUBLE) {
                format = doubleFormat;
                if (format != null) {
                    dataString = (((double)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == INT16) {
                format = integerFormat;
                if (format != null) {
                    dataString = (((Int16)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == UINT16) {
                format = integerFormat;
                if (format != null) {
                    dataString = (((UInt16)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == UINT32) {
                format = integerFormat;
                if (format != null) {
                    dataString = (((uint)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == UINT64) {
                format = longFormat;
                if (format != null) {
                    dataString = (((ulong)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == BYTE) {
                format = integerFormat;
                if (format != null) {
                    dataString = (((byte)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == SBYTE) {
                format = integerFormat;
                if (format != null) {
                    dataString = (((sbyte)data).ToString(format));
                }
                else {
                    dataString = (data.ToString());
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else if (dataType == CHAR) {
                dataString = QUOTE_CHAR + EscapeString(data.ToString()) + QUOTE_CHAR;
                sb.Append(dataString);
                return dataString.Length;
            }
            else {
                return 0;
            }
        }
        else if (dataType == STRING) {
            dataString = QUOTE_CHAR + EscapeString((string)data) + QUOTE_CHAR;
            sb.Append(dataString);
            return dataString.Length;
        }
        else if (dataType == DATE_TIME) {
            StringBuilder dsb = new StringBuilder(35);
            dsb.Append(APOSTROPHE_CHAR);
            string format = dateTimeFormat;
            if (format != null) {
                dsb.Append(EscapeDateTime(((DateTime)data).ToString(format)));
            }
            else {
                DateTime dateTime = (DateTime)data;
                int year = dateTime.Year;
                if (year < yearStrings.Length) {
                    dsb.Append(yearStrings[year]).Append(HYPHEN_CHAR);
                }
                else {
                    dsb.Append(year).Append(HYPHEN_CHAR);
                }
                dsb.Append(yearStrings[dateTime.Year]).Append(HYPHEN_CHAR);
                dsb.Append(dateStrings[dateTime.Month]).Append(HYPHEN_CHAR);
                dsb.Append(dateStrings[dateTime.Day]).Append(SPACE_CHAR);
                dsb.Append(dateStrings[dateTime.Hour]).Append(COLON_CHAR);
                dsb.Append(dateStrings[dateTime.Minute]).Append(COLON_CHAR);
                dsb.Append(dateStrings[dateTime.Second]).Append(PERIOD_CHAR);
                dsb.Append(millisecondStrings[dateTime.Millisecond]);
                dsb.Append(GetLocalTimeZone(dateTime.Month, dateTime.Day));
            }
            dsb.Append(APOSTROPHE_CHAR);
            dataString = dsb.ToString();
            sb.Append(dataString);
            return dataString.Length;
        }
        else if (dataType == DECIMAL) {
            string format = decimalFormat;
            if (format != null) {
                dataString = (((decimal)data).ToString(format));
            }
            else {
                dataString = (data.ToString());
            }
            sb.Append(dataString);
            return dataString.Length;
        }
        else if (dataType == TIMESPAN) {
            StringBuilder dsb = new StringBuilder(25);
            dsb.Append(APOSTROPHE_CHAR);
            TimeSpan ts = (TimeSpan)data;
            dsb.Append(P_CHAR);
            dsb.Append(ts.Days).Append(DT);
            dsb.Append(ts.Hours).Append(H_CHAR);
            dsb.Append(ts.Minutes).Append(M_CHAR);
            dsb.Append(ts.Seconds);
            if (ts.Milliseconds > 0) {
                dsb.Append(PERIOD_CHAR).Append(millisecondStrings[ts.Milliseconds]);
            }
            dsb.Append(S_CHAR);
            dsb.Append(APOSTROPHE_CHAR);
            dataString = dsb.ToString();
            sb.Append(dataString);
            return dataString.Length;
        }
        else if (dataType.IsArray) {
            sb.Append(LIST_MARKER);
            return LIST_MARKER.Length;
        }
        else if (data is IDictionary) {
            sb.Append(MAP_MARKER);
            return MAP_MARKER.Length;
        }
        else if (data is IEnumerable) {
            sb.Append(LIST_MARKER);
            return LIST_MARKER.Length;
        }
        else {
            TypeFormatter formatter = this.GetTypeFormatter(dataType);
            if (formatter != null) {
                if (formatter.IsPassThroughRequested) {
                    dataString = formatter.Format(data);
                }
                else {
                    dataString = QUOTE_CHAR + formatter.Format(data) + QUOTE_CHAR;
                }
                sb.Append(dataString);
                return dataString.Length;
            }
            else {
                dataString = QUOTE_CHAR + EscapeString(data.ToString()) + QUOTE_CHAR;
                sb.Append(dataString);
                return dataString.Length;
            }
        }
    }
   
    private static string EscapeDateTime(string s) {
        if (s.IndexOf(BACKSLASH_CHAR) >= 0) {
            s = s.Replace(BACKSLASH, BACKSLASH_ESCAPE);
        }
        if (s.IndexOf(APOSTROPHE_CHAR) >= 0) {
            s = s.Replace(APOSTROPHE, APOSTROPHE_ESCAPE);
        }
        if (s.IndexOf('\n') >= 0) {
            s = s.Replace(NEW_LINE_VALUE, NEW_LINE_ESCAPE);
        }
        return s;
    }

    private static string EscapeString(string s) {
        if (s == null) {
            return null;
        }
        if (s.IndexOf(BACKSLASH_CHAR) >= 0) {
            s = s.Replace(BACKSLASH, BACKSLASH_ESCAPE);
        }
        if (s.IndexOf(QUOTE_CHAR) >= 0) {
            s = s.Replace(QUOTE, QUOTE_ESCAPE);
        }
        if (s.IndexOf(NEW_LINE_CHAR) >= 0) {
            s = s.Replace(NEW_LINE_VALUE, NEW_LINE_ESCAPE);
        }
        return s;
    }
    
    private class StackNode {
        public int Position;
        public bool IsAtEnd;
        
        private StackNode() {}
        public StackNode(int position, bool isAtEnd) {
            Position = position;
            IsAtEnd = isAtEnd;
        }
    }
    
}

}

